A take home reference for the R you will need across this course. You already have a dedicated R programming course, so nothing here is taught in class. Flip to the relevant section whenever you get stuck during a lab or exam.
Instructor: K.M. Tanvir • Institute of Statistical Research and Training (ISRT), University of Dhaka
Install R from cran.r-project.org (the actual language) and RStudio Desktop from posit.co (a friendly editor around it). Open RStudio; the console at the bottom is where you type commands.
R has a "current folder" it reads from and writes to. Check and change it with:
getwd() # where am I?
setwd("/Users/you/Desktop/ast232") # go there (macOS / Linux)
setwd("C:/Users/you/Desktop/ast232") # go there (Windows)
.R or .Rmd file there, then use Session → Set Working Directory → To Source File Location. All your paths become simple: read.csv("data.csv") just works.
| Symbol | Meaning | Example |
|---|---|---|
# | Comment, everything after # is ignored | # this is a note |
<- | Assignment (preferred in R) | x <- 5 |
= | Also assignment, plus used for function arguments | mean(x, na.rm = TRUE) |
+ - * / | Arithmetic | 3 + 4 * 2 |
^ | Exponent | 2 ^ 10 gives 1024 |
%% | Modulo (remainder) | 17 %% 5 gives 2 |
%/% | Integer division | 17 %/% 5 gives 3 |
x <- 5 # store 5 in x
y <- 3 # store 3 in y
x + y # prints 8 to the console
z <- x + y # store 8 in z, print nothing
print(z) # prints 8 explicitly
| Type | Example | Check with |
|---|---|---|
| numeric (double) | 3.14, 0.005 | is.numeric(x) |
| integer | 5L, 100L | is.integer(x) |
| character | "male", "V1" | is.character(x) |
| logical | TRUE, FALSE | is.logical(x) |
| factor | factor(c("A","B","A")) | is.factor(x) |
| missing | NA, NA_real_, NA_integer_ | is.na(x) |
| null | NULL | is.null(x) |
ANOVA treats a factor differently from a plain number. Always convert treatment / block / group columns to factors before aov():
treat <- c("V1", "V2", "V3", "V1", "V2", "V3")
treat <- factor(treat)
levels(treat) # "V1" "V2" "V3"
is.na(x) to test for missing values, not x == NA.
The vector is the fundamental R object. Even a single number is a vector of length 1.
x <- c(1, 2, 3, 4, 5) # combine
y <- 1:10 # sequence 1 to 10
z <- seq(0, 1, by = 0.1) # 0.0, 0.1, ..., 1.0
w <- seq(0, 100, length.out = 11) # 0, 10, 20, ..., 100
r <- rep("A", times = 5) # "A" "A" "A" "A" "A"
g <- rep(c("V1", "V2", "V3"), each = 4) # V1 V1 V1 V1 V2 V2 ... V3 V3
h <- rep(1:4, times = 3) # 1 2 3 4 1 2 3 4 1 2 3 4
a <- c(1, 2, 3)
b <- c(10, 20, 30)
a + b # 11 22 33
a * 2 # 2 4 6 (scalar broadcasts)
sqrt(a) # 1.000 1.414 1.732
length(x) # number of elements
sum(x) # total
mean(x) # mean
min(x); max(x)
range(x) # c(min, max)
sort(x) # sorted vector
rev(x) # reverse
cumsum(x) # cumulative sums, used in Life Table Tx
cumprod(x) # cumulative products, used in Life Table lx
A data frame is a rectangular table where each column can be a different type. It is the R equivalent of a spreadsheet.
# Build one from vectors
age <- c(18, 22, 30, 45)
gender <- c("F", "M", "F", "M")
height <- c(160, 175, 162, 180)
df <- data.frame(age, gender = factor(gender), height)
df
# age gender height
# 1 18 F 160
# 2 22 M 175
# 3 30 F 162
# 4 45 M 180
head(df, n = 6) # first 6 rows
tail(df, n = 6) # last 6 rows
str(df) # structure: types and first values
summary(df) # quick summary per column
names(df) # column names
nrow(df); ncol(df)
dim(df) # c(nrow, ncol)
df$bmi <- 70 / (df$height / 100)^2 # assume weight 70 kg
[ ], [[ ]], $x <- c(10, 20, 30, 40, 50)
x[3] # 30 (third element)
x[2:4] # 20 30 40
x[c(1, 3, 5)] # 10 30 50 (integer index)
x[-1] # drop the first element
x[x > 25] # 30 40 50 (logical index)
df[1, ] # first row, all columns
df[, 2] # second column, all rows (returns vector)
df[, "gender"] # same, by name
df$gender # same, dollar shortcut
df[df$age > 25, ] # all rows where age > 25
df[df$gender == "F", c("age", "height")] # female rows, two columns
[ ] vs [[ ]] in a nutshellx[1] returns a subset of the same type. On a data frame it returns a data frame.x[[1]] extracts the element itself. On a data frame it returns the column as a vector.x$name is a shortcut for x[["name"]]. Works on data frames and lists.# Read
dat <- read.csv("my_data.csv")
dat <- read.csv("my_data.csv", header = TRUE,
stringsAsFactors = FALSE)
# Write
write.csv(dat, "output.csv", row.names = FALSE)
install.packages("readxl") # once
library(readxl)
dat <- read_excel("my_data.xlsx")
dat <- read_excel("my_data.xlsx", sheet = "Sheet2")
# .RData: save multiple objects together
save(dat, model, file = "analysis.RData")
load("analysis.RData") # restores dat, model into workspace
# .rds: one object at a time, cleaner
saveRDS(model, "model.rds")
model <- readRDS("model.rds")
if, for, whilex <- 10
if (x > 5) {
print("big")
} else if (x == 5) {
print("exactly five")
} else {
print("small")
}
ifelse()x <- c(2, 7, 4, 9)
ifelse(x > 5, "big", "small")
# "small" "big" "small" "big"
for (i in 1:5) {
print(i^2)
}
# Fill a vector inside a loop (pre allocate)
result <- numeric(10)
for (i in 1:10) {
result[i] <- i^2
}
i <- 1
while (i <= 5) {
print(i)
i <- i + 1
}
x * 2 is faster and clearer than looping over each element and multiplying. Reach for a loop only when you cannot express the task vectorised.
The syntax is function_name <- function(arg1, arg2, ...) { ... body ... }. The last expression evaluated is the return value (you can also use return()).
# A tiny function
square <- function(x) {
x ^ 2
}
square(7) # 49
# Default arguments
mean_or_median <- function(x, use_median = FALSE) {
if (use_median) median(x) else mean(x)
}
# A function that returns multiple values via a named list
summary_stats <- function(x) {
list(
n = length(x),
mean = mean(x),
sd = sd(x),
range = range(x)
)
}
summary_stats(c(1, 4, 5, 6, 8))
build_life_table() in Module 4 and mortality_report() in Module 3 are both custom functions that bundle a workflow into one reusable name.
These functions apply another function across the elements of a vector, list, or margin of a matrix / data frame. They replace many for loops.
| Function | Applies to | Returns | Example |
|---|---|---|---|
apply(mat, MARGIN, FUN) | Matrix or data frame | Vector or matrix | apply(m, 1, sum) row sums |
sapply(x, FUN) | Vector or list | Simplified vector / matrix | sapply(1:5, function(i) i^2) |
lapply(x, FUN) | Vector or list | List (always) | lapply(mylist, mean) |
tapply(x, group, FUN) | Vector split by group | Named vector / array | tapply(yield, treat, mean) |
mapply(FUN, x, y, ...) | Multiple vectors in parallel | Vector | mapply(sum, 1:3, 4:6) |
tapply(y, group, FUN) is the natural R idiom. It appears in every CRD, RCBD, and LSD analysis.
| Function | Meaning |
|---|---|
mean(x) | Arithmetic mean |
median(x) | Median |
var(x) | Sample variance (divides by n − 1) |
sd(x) | Sample standard deviation |
quantile(x, probs) | Quantiles at the given probabilities |
IQR(x) | Interquartile range Q3 − Q1 |
cor(x, y) | Pearson correlation |
cov(x, y) | Covariance |
summary(x) | Five number summary plus mean |
table(x) | Frequency table |
prop.table(table(x)) | Proportions instead of counts |
na.rm = TRUE | Argument to skip NA values, e.g. mean(x, na.rm = TRUE) |
Every standard distribution comes with four functions, prefixed d (density), p (cumulative), q (quantile / inverse cumulative), and r (random draws).
| Distribution | d / p / q / r | What you use it for |
|---|---|---|
| Normal(μ, σ) | dnorm, pnorm, qnorm, rnorm | Z tests, confidence intervals |
| Student t | dt, pt, qt, rt | t tests, one at a time CIs |
| F(df1, df2) | df, pf, qf, rf | ANOVA F tests |
| Chi squared(df) | dchisq, pchisq, qchisq, rchisq | Goodness of fit, variance CIs |
| Binomial(n, p) | dbinom, pbinom, qbinom, rbinom | Yes / no counts |
| Poisson(λ) | dpois, ppois, qpois, rpois | Event counts (birth or death counts in demography) |
dnorm(1.96) # density (height of the curve) at x = 1.96
pnorm(1.96) # P(Z <= 1.96) = 0.975
qnorm(0.975) # quantile: value with P below it of 0.975 = 1.96
rnorm(5, mean = 0, sd = 1) # 5 random draws
qf(0.95, df_treat, df_error). A one at a time CI in Module 5 uses qt(0.975, df_error). Bonferroni CIs replace 0.975 with 1 − α / (2r).
aov()A formula in R uses ~ to separate the response (left) from the predictors (right).
| Formula | Meaning | Design |
|---|---|---|
y ~ treat | Response depends on one factor | CRD |
y ~ treat + block | Response depends on treatment and block (main effects) | RCBD |
y ~ treat + row + column | Three main effects | Latin Square |
y ~ treat * block | Main effects plus interaction (equivalent to treat + block + treat:block) | Factorial (not covered) |
y ~ . | Response against every other column in the data | Regression shortcut |
model <- aov(yield ~ treat + block, data = df)
summary(model) # ANOVA table with F and p
residuals(model) # residuals for diagnostics
fitted(model) # fitted values
coef(model) # coefficients (contrast form)
TukeyHSD(model, "treat") # pairwise comparison, family wise controlled
| Function | What it draws | Where it appears |
|---|---|---|
plot(x, y) | Scatter or line plot | ASFR and ASDR curves, ex curve |
boxplot(y ~ x) | Boxplot by group | CRD, RCBD visualisation |
hist(x) | Histogram | Residual distribution check |
barplot(x) | Bar chart, accepts negatives | Population pyramid |
qqnorm(x); qqline(x) | Normal QQ plot | Normality check in ANOVA |
interaction.plot() | Mean of y by two factors | RCBD dye trial exercise |
plot(model) | Four diagnostic panels | Shortcut for CRD / RCBD checks |
plot(x, y,
type = "o", # "p" points, "l" lines, "b" both, "o" over-plotted
pch = 19, # point shape (19 = filled circle)
col = "#2563eb", # colour (name, hex, or rgb)
lwd = 2, # line width
xlab = "Age group", # axis labels
ylab = "Rate",
main = "My plot", # title
xlim = c(0, 50), # axis range
ylim = c(0, 100))
points(x, y2, col = "red") # overlay more points
lines(x, y3, col = "green") # overlay a line
abline(h = 0, lty = 2) # horizontal dashed reference line
legend("topright",
legend = c("A", "B"),
col = c("red", "green"),
lty = 1, lwd = 2)
par(mfrow = c(1, 2)) # 1 row, 2 columns
qqnorm(res); qqline(res)
plot(fit, res); abline(h = 0, lty = 2)
par(mfrow = c(1, 1)) # reset back to single plot
If treat is a character vector (not a factor), aov() may still work but printing coefficients gets confusing. Convert with factor() before modelling.
mean(x) where x contains a single NA returns NA. Add na.rm = TRUE: mean(x, na.rm = TRUE).
Very large integer arithmetic can overflow. Use doubles: multiply by 1.0 to force conversion.
Yield is not the same as yield. R errors will complain "object not found". Check your capitalisation.
5 -> x is legal R (right assignment) but almost always a mistake. Stick with <- or =.
After par(mfrow = c(2, 2)), R stays in 2x2 layout until you set it back to c(1, 1) or restart R.
If read.csv("data.csv") fails with "cannot open connection", your working directory is not what you think. Check with getwd(), fix with setwd() or (better) use RStudio's Session → Set Working Directory → To Source File Location.
Installed once with install.packages("readxl"), but you still need library(readxl) at the top of every script that uses it.